use super::{
    cache::{update_it, update_plc},
    err, err_value,
    file::{list_mod, rm_file, scan_mod},
    get_file, it_dir, json, mod_dir, ok, throw_err, Base, ECode, JsonFile, LockFile, MapSv, RNull,
    Resp, SvCache, VBase, VMap, Value,
};
use std::collections::HashSet;
use tracing::{instrument, warn};

pub struct Cfg;

impl Base for Cfg {}

impl Cfg {
    pub async fn do_cmd(&self, para: &Value, cmd: &str, lock: &LockFile, cache: SvCache) -> Resp {
        let data = para.ckobj()?;

        match cmd {
            "add" => self.do_update(data, lock, cache, true).await,
            "copy" => self.do_copy(data, lock, cache).await,
            "update" => self.do_update(data, lock, cache, false).await,
            "del" => self.do_del(data, lock, cache).await,
            "file" => self.do_file(data, lock).await,
            _ => throw_err!(ECode::NoServe, "no cfg serve"),
        }
    }

    async fn do_file(&self, data: &MapSv, lock: &LockFile) -> Resp {
        let path = data.ck_str("p")?;

        let file = match data.k_str("md") {
            Some(v) => mod_dir(v, path),
            None => get_file(path),
        };

        let r = lock.read().await;

        let body = JsonFile::read_file(&file)?;

        drop(r);

        ok!(body)
    }

    async fn do_del(&self, data: &MapSv, lock: &LockFile, cache: SvCache) -> Resp {
        let md = data.ck_str("mod")?;
        let cache_id = data.ck_str("uid")?;
        let path = mod_dir(md, cache_id);

        let r = lock.write().await;

        let _ = rm_file(&path)?;

        if md == "plc" {
            if let Some(fs) = list_mod("it") {
                for f in fs.iter() {
                    if f.starts_with(&cache_id) {
                        let p = mod_dir("it", f);
                        let _ = rm_file(&p)?;
                    }
                }
            }
        }

        drop(r);

        let _ = self.del_mod(cache, md).await?;

        ok!()
    }

    #[instrument(skip_all, name = "do_copy")]
    async fn do_copy(&self, data: &MapSv, lock: &LockFile, cache: SvCache) -> Resp {
        let org_plc = data.ck_str("plc")?;
        let val = data.ck_obj("v")?;

        let r = lock.write().await;

        let list = match list_mod("it") {
            Some(v) => v,
            None => Vec::new(),
        };

        let new_plc = val.ck_str("uid")?;
        let file = mod_dir("plc", new_plc);

        let val = json!(val);
        JsonFile::write_file(&file, &val)?;

        for d in list.iter() {
            if !d.starts_with(org_plc) {
                continue;
            }

            let f = it_dir(d);

            let mut body = JsonFile::read_file(&f)?;

            if let Some(body) = body.as_object_mut() {
                let uid = body.ck_str("uid")?;
                let uid = uid.replacen(org_plc, new_plc, 1);

                body.insert("plc".to_string(), json!(new_plc));
                body.insert("uid".to_string(), json!(uid));
                body.insert("active".to_string(), json!(false));

                let nf = it_dir(&uid);
                let body = json!(body);

                JsonFile::write_file(&nf, &body)?;
            }
        }

        drop(r);

        let _ = self.update_copy(cache).await?;

        ok!()
    }

    #[instrument(skip_all, name = "do_update")]
    async fn do_update(&self, data: &MapSv, lock: &LockFile, cache: SvCache, add: bool) -> Resp {
        let md = data.ck_str("mod")?;
        let val = data.ck_array("v")?;

        let mut ret = Vec::new();

        let r = lock.write().await;

        for d in val.iter() {
            let cache_id = d.ck_str("uid")?;
            let file = mod_dir(md, cache_id);

            if let Err(e) = JsonFile::write_file(&file, d) {
                warn!(target: "保存文件错误", "cfg update error: {:?}", e);
                continue;
            }

            ret.push(d);
        }

        drop(r);

        let _ = self.update_mod(cache, md, &ret, add).await?;

        ok!(ret)
    }

    async fn update_mod(&self, cache: SvCache, md: &str, body: &Vec<&Value>, add: bool) -> RNull {
        match md {
            "plc" => self.scan_plc(&body, &cache, add).await,
            "it" => self.scan_it(&cache, body, add).await,
            _ => Ok(()),
        }
    }

    async fn del_mod(&self, cache: SvCache, md: &str) -> RNull {
        match md {
            "plc" => {
                self.clear_cache("plc", &cache).await;
                self.clear_cache("it", &cache).await;

                Ok(())
            }
            "it" => {
                self.clear_cache("it", &cache).await;
                Ok(())
            }
            _ => Ok(()),
        }
    }

    async fn scan_it(&self, cache: &SvCache, body: &Vec<&Value>, add: bool) -> RNull {
        for d in body.iter() {
            let d = d.ckobj()?;
            let cache_id = d.ck_str("uid")?;

            cache.insert(cache_id.to_string(), json!(d)).await;
        }

        if add {
            let _ = scan_mod("it", cache).await;
        }

        Ok(())
    }

    async fn scan_plc(&self, body: &Vec<&Value>, cache: &SvCache, add: bool) -> RNull {
        for d in body.iter() {
            let cache_id = d.ck_str("uid")?;
            cache.insert(cache_id.to_string(), json!(d)).await;
        }

        if add {
            let _ = scan_mod("plc", cache).await;
        }

        Ok(())
    }

    async fn clear_cache(&self, k: &str, cache: &SvCache) -> Option<()> {
        let list = match list_mod(k) {
            Some(v) => v,
            None => Vec::new(),
        };

        let mut set = HashSet::new();

        for d in list.iter() {
            set.insert(d.to_string());
        }

        let old = cache.get(k)?;
        let old = old.array()?;

        for d in old.iter() {
            let cache_id = d.kstr()?;

            if set.contains(cache_id) {
                continue;
            }

            cache.invalidate(cache_id).await;
        }

        cache.insert(k.to_string(), json!(list)).await;

        Some(())
    }

    async fn update_copy(&self, cache: SvCache) -> RNull {
        let _ = update_plc(&cache).await?;
        let _ = update_it(&cache).await?;

        Ok(())
    }
}
